package com.gitee.l0km.beanfilter.core.json;

import java.io.IOException;
import java.util.ArrayList;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBase;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializer;
import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider;
import com.fasterxml.jackson.databind.ser.PropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.gitee.l0km.beanfilter.core.CodecPhase;
import com.gitee.l0km.beanfilter.core.CodecScene;
import com.gitee.l0km.beanfilter.core.Filterable;
import com.gitee.l0km.beanfilter.core.IFieldFilter;
import com.gitee.l0km.beanfilter.core.IFilter;
import com.gitee.l0km.beanfilter.core.LocalSupplyFilter;
import com.gitee.l0km.beanfilter.core.RequestPhase;
import com.gitee.l0km.beanfilter.core.SupplyFilter;
import com.gitee.l0km.beanfilter.core.context.LocalContext;
import com.google.common.base.Supplier;

/**
 * dynamic field filter implementation of Jackson {@link JsonSerializer},<br>
 * MAVEN dependencies:
 * <pre>
 *		&lt;dependency&gt;
 *			&lt;groupId&gt;com.fasterxml.jackson.core&lt;/groupId&gt;
 *			&lt;artifactId&gt;jackson-databind&lt;/artifactId&gt;
 *			&lt;version&gt;${jackson.version}&lt;/version&gt;
 *		&lt;/dependency&gt;
 * </pre>
 * 
 * @author guyadong
 */
public class JacksonFilterableSerializer extends BeanSerializer implements Filterable {
	private static final long serialVersionUID = 1224417865395279034L;
	private final LocalSupplyFilter localSupplyFilter;
	public JacksonFilterableSerializer(Class<?> beanClass) {
		this(createBeanSerializer(beanClass));
	}
	private JacksonFilterableSerializer(JacksonBeanSerializerBuilder builder) {
		super(builder.getBeanDescription().getType(),
				builder,builder.buildProperties(),builder.getFilteredProperties());
		this.localSupplyFilter = new LocalSupplyFilter(handledType(), RequestPhase.SERIALIZING, CodecScene.JACKSON);
	}
	protected SupplyFilter getSupplyFilter() {
		return localSupplyFilter.getSupplyFilter();
	}
	@Override
	public void injectFilter(Supplier<? extends IFilter> fieldFilterSupplier, Supplier<? extends IFilter> valueFilterSupplier, CodecPhase codecPhase){
		if(null != fieldFilterSupplier || null !=valueFilterSupplier) {
			SupplyFilter supplyFilter =  SupplyFilter.combine2(fieldFilterSupplier,valueFilterSupplier);
			localSupplyFilter.setSupplyFilter(supplyFilter);
		}
	}
	@Override
    protected void serializeFieldsFiltered(Object bean, JsonGenerator gen,
            SerializerProvider provider)
        throws IOException, JsonGenerationException
    {
		serializeFields(bean, gen, provider);
    }
	@Override
    protected void serializeFields(Object bean, JsonGenerator gen, SerializerProvider provider)
            throws IOException
	{
		try {
			LocalContext.enteringEnv(bean, RequestPhase.SERIALIZING, CodecScene.JACKSON);
			doSerializeFields(bean, gen, provider);
		} finally {
			LocalContext.exitingEnv();
		}
	}
	protected void doSerializeFields(Object bean, JsonGenerator gen, SerializerProvider provider)
			throws IOException
	{
		final BeanPropertyWriter[] props;
		if (_filteredProps != null && provider.getActiveView() != null) {
			props = filterProperty(_filteredProps);
		} else {
			props = filterProperty(_props);
		}
		final PropertyFilter filter = null == _propertyFilterId ? null : findPropertyFilter(provider, _propertyFilterId, bean);
		int i = 0;
		try {
			for (final int len = props.length; i < len; ++i) {
				BeanPropertyWriter prop = props[i];
				if (prop != null) { // can have nulls in filtered list
					if(getSupplyFilter().getValueFilter() != null) {
						/** 
						 * Writer 为非静态类，
						 * 虽然构造方法中只声明了一个参数，但实际为两个参数
						 * 第一个为隐藏参数，为所属实例 
						 */
						prop = new Writer(prop);
					}
					if(null!=filter) {
						filter.serializeAsField(bean, gen, provider, prop);
					}else {
						prop.serializeAsField(bean, gen, provider);
					}
				}
			}
			if (_anyGetterWriter != null) {
				if(null!=filter) {
					_anyGetterWriter.getAndSerialize(bean, gen, provider);
				}else {
					_anyGetterWriter.getAndFilter(bean, gen, provider, filter);
				}
			}
		} catch (Exception e) {
			String name = (i == props.length) ? "[anySetter]" : props[i].getName();
			wrapAndThrow(provider, e, bean, name);
		} catch (StackOverflowError e) {
			/* 04-Sep-2009, tatu: Dealing with this is tricky, since we do not
			 *   have many stack frames to spare... just one or two; can't
			 *   make many calls.
			 */
			// 10-Dec-2015, tatu: and due to above, avoid "from" method, call ctor directly:
			//JsonMappingException mapE = JsonMappingException.from(gen, "Infinite recursion (StackOverflowError)", e);
			JsonMappingException mapE = new JsonMappingException(gen, "Infinite recursion (StackOverflowError)", e);
			
			String name = (i == props.length) ? "[anySetter]" : props[i].getName();
			mapE.prependPath(new JsonMappingException.Reference(bean, name));
			throw mapE;
		}
	}
	private BeanPropertyWriter[] filterProperty(BeanPropertyWriter[] input) {
		IFieldFilter filter = getSupplyFilter().getFieldFilter();
		if(null != filter) {
			ArrayList<BeanPropertyWriter> list = new ArrayList<>();
			for(BeanPropertyWriter writer:input) {
				if(getSupplyFilter().permit(handledType(), writer.getName())) {
					list.add(writer);
				}
			}
			return list.toArray(new BeanPropertyWriter[0]);
		}
		return input;
	}
	@SuppressWarnings("serial")
	class Writer extends BeanPropertyWriter{
		public Writer(BeanPropertyWriter base) {
			super(base);
//			SimpleLog.log("{} for {}",getClass().getSimpleName(),base.getName());
		}

		@Override
		public void serializeAsField(Object bean, JsonGenerator gen, SerializerProvider prov) throws Exception {
	        final Object value = getSupplyFilter().process(handledType(), getName(), get(bean));
	        
	        /** 全部复制自父类方法实现 */
	        // Null handling is bit different, check that first
	        if (value == null) {
	            if (_nullSerializer != null) {
	                gen.writeFieldName(_name);
	                _nullSerializer.serialize(null, gen, prov);
	            }
	            return;
	        }
	        // then find serializer to use
	        JsonSerializer<Object> ser = _serializer;
	        if (ser == null) {
	            Class<?> cls = value.getClass();
	            PropertySerializerMap m = _dynamicSerializers;
	            ser = m.serializerFor(cls);
	            if (ser == null) {
	                ser = _findAndAddDynamic(m, cls, prov);
	            }
	        }
	        // and then see if we must suppress certain values (default, empty)
	        if (_suppressableValue != null) {
	            if (MARKER_FOR_EMPTY == _suppressableValue) {
	                if (ser.isEmpty(prov, value)) {
	                    return;
	                }
	            } else if (_suppressableValue.equals(value)) {
	                return;
	            }
	        }
	        // For non-nulls: simple check for direct cycles
	        if (value == bean) {
	            // four choices: exception; handled by call; pass-through or write null
	            if (_handleSelfReference(bean, gen, prov, ser)) {
	                return;
	            }
	        }
	        gen.writeFieldName(_name);
	        if(!getType().isPrimitive() && !getType().isTypeOrSuperTypeOf(value.getClass())) {
	        	ser = prov.findValueSerializer(value.getClass());
	        }
	        if (_typeSerializer == null) {
	            ser.serialize(value, gen, prov);
	        } else {
	            ser.serializeWithType(value, gen, prov, _typeSerializer);
	        }
	    }
		
	}
	/**
	 * 创建{@code beanClass}对应的{@link BeanDeserializerBase}实例用于父类构造方法的参数,
	 * 将{@code beanClass}的序列化参数注入到当前实例中
	 * @param beanClass
	 */
	private static JacksonBeanSerializerBuilder createBeanSerializer(Class<?> beanClass){
		try {
			JavaType type = TypeFactory.defaultInstance().constructType(beanClass);
			ObjectMapper mapper = new ObjectMapper();
			BeanDescription desc = mapper.getSerializationConfig().introspect(type);
			SerializerProvider provider = ((DefaultSerializerProvider) mapper.getSerializerProvider())
							.createInstance(mapper.getSerializationConfig(), JacksonBeanSerializerFactory.instance);
			return JacksonBeanSerializerFactory.instance
					.constructBeanSerializerBuilder(provider, desc);
		} catch (Exception e) {
			throw new ExceptionInInitializerError(e);
		}
	}
}
